﻿using System;
using OverStore.Runtime.PersistenceServiceProvider;
using OverStore.Runtime.Storage;
using OverStore.Runtime;
using System.Globalization;
using System.Reflection;
using OverStore.Runtime.ObjectRepository.Mapping;

namespace OverStore.Configuration.ObjectRepository.Mapping
{
    /// <summary>
    /// Base class implements <see cref="IMapper{Tinstance}"/> and 
    /// component configuration for mapper intended to be used 
    /// as base class for creating high-performance mappers for object of specified type.
    /// </summary>
    /// <typeparam name="TInstance">The type of the instance.</typeparam>
    public abstract class MapperBase<TInstance> : IMapper<TInstance>, IComponentConfiguration<IMapper<TInstance>>
        where TInstance : class
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="MapperBase{TInstance}"/> class.
        /// </summary>
        protected MapperBase()
        {
        }

        /// <summary>
        /// Get data of object.
        /// Operation should not checks arguments for null etc for
        /// gain performance.
        /// </summary>
        /// <param name="registry">The object allows to perform persistence operations over instances of different types.</param>
        /// <param name="instance">The instance to get data.</param>
        /// <returns>The data of instance.</returns>
        /// <exception cref="PersistingRuntimeException">Error occured during mapping.</exception>
        public IRecord GetObjectData(TInstance instance, IRegistry registry)
        {
            return new InternalRecord(instance, this, registry);
        }

        /// <summary>
        /// Apply data to object.
        /// Operation should not checks arguments for null etc for
        /// gain performance.
        /// </summary>
        /// <param name="data">The data for apply to result.</param>
        /// <param name="result">The result object for apply data to it.</param>
        /// <param name="registry">The object allows to perform persistence operations over instances of different types.</param>
        /// <exception cref="PersistingRuntimeException">Error occured during mapping.</exception>
        public void ApplyObjectData(TypedRecord data, TInstance result, IRegistry registry)
        {
            try
            {
                ApplyObjectDataCore(data, result, registry);
            }
            catch (Exception ex)
            {
                throw new PersistingRuntimeException(
                    String.Format(CultureInfo.InvariantCulture, "Mapping Error: applying object data to the instance of type {0} failed.", typeof(TInstance)),
                    ex);
            }
        }

        /// <summary>
        /// Creates instance of <see cref="IMapper{TInstance}"/> with specified configuration.
        /// </summary>
        /// <param name="context">Context contains references to services required for <see cref="IMapper{TInstance}"/> instance.</param>
        /// <returns>
        ///     <see cref="IMapper{TInstance}"/> instance with specified configuration.
        /// </returns>
        public IMapper<TInstance> CreateComponent(OverStore.Runtime.OverStoreContext.IOverStoreContext context)
        {
            var result = (MapperBase<TInstance>)MemberwiseClone();
            return result;
        }

        /// <summary>
        /// Applies the object data. 
        /// </summary>
        /// <param name="data">The data.</param>
        /// <param name="result">The result.</param>
        /// <param name="registry">The object allows to perform persistence operations over instances of different types.</param>
        protected abstract void ApplyObjectDataCore(TypedRecord data, TInstance result, IRegistry registry);

        /// <summary>
        /// Gets the result with specified name from object data .
        /// </summary>
        /// <param name="instance">The instance.</param>
        /// <param name="name">The name of result.</param>
        /// <param name="registry">The object allows to perform persistence operations over instances of different types.</param>
        /// <returns>The result.</returns>
        protected abstract object GetObjectDataCore(TInstance instance, string name, IRegistry registry);

        private class InternalRecord : IRecord
        {
            private readonly TInstance Instance;
            private readonly IRegistry Registry;
            private readonly MapperBase<TInstance> Mapper;

            public InternalRecord(TInstance instance, MapperBase<TInstance> mapper, IRegistry registry)
            {
                Instance = instance;
                Mapper = mapper;
                Registry = registry;
            }

            public bool TryGetValue(string name, out object result)
            {
                try
                {
                    result = Mapper.GetObjectDataCore(Instance, name, Registry);
                    if (ReferenceEquals(result, Missing.Value /* MissingValue.Value */))
                        return false;
                    else
                        return true;
                }
                catch (Exception ex)
                {
                    throw new PersistingRuntimeException(
                        String.Format(CultureInfo.InvariantCulture, "Mapping Error: applying object data to the instance of type {0} failed.", typeof(TInstance)),
                        ex);
                }
            }
        }
    }
}
